<template>
  <t-chat
    layout="both"
    style="height: 600px"
    :clear-history="false"
    :reverse="true"
    :text-loading="loading"
  >
    <template v-for="(item, index) in chatList" :key="index">
      <t-chat-item
        :avatar="item.avatar"
        :name="item.name"
        :role="item.role"
        :datetime="item.datetime"
        :content="item.content"
        :text-loading="index === 0 && loading"
        :variant="item.variant"
      >
      </t-chat-item>
    </template>
    <template #footer>
      <t-chat-sender
        :stop-disabled="isStreamLoad"
        :textarea-props="{
          placeholder: '请输入消息...',
        }"
        @send="sendMessage"
        @stop="stopLoad"
      >
        <template #prefix>
          <div>
            <t-tooltip
              v-model:visible="allowToolTip"
              content="切换模型"
              trigger="hover"
            >
              <t-select
                v-model="selectValue"
                :options="selectOptions"
                value-type="object"
                @focus="allowToolTip = false"
              ></t-select>
            </t-tooltip>
          </div>
        </template>
      </t-chat-sender>
    </template>
  </t-chat>
</template>
<script setup>
import { ref } from "vue";

// 下拉框数据
const selectOptions = [
  {
    label: "deepseek-r1:8b",
    value: "deepseek-r1:8b",
  },
  {
    label: "deepseek-r1:14b",
    value: "deepseek-r1:14b",
  },
  {
    label: "deepseek-r1:32b",
    value: "deepseek-r1:32b",
  },
];

// 下拉框选中值
const selectValue = ref({
  label: "deepseek-r1:8b",
  value: "deepseek-r1:8b",
});

// 允许ToolTip
const allowToolTip = ref(false);

// 聊天列表，用于存储用户和助手的消息
const chatList = ref([
  {
    content: `模型由 ollama 提供数据支持`,
    role: "model-change",
    variant: "base",
  },
]);
// 加载状态
const loading = ref(false);
// 是否流在加载中
const isStreamLoad = ref(false);
// AbortController对象用于取消SSE
const fetchAbort = ref(null);

/**
 * 发送消息功能
 * 此函数用于在聊天列表中添加用户和助手的消息
 * 它首先检查是否正在加载流数据，如果是，则不执行任何操作
 * 否则，它会创建用户和助手的消息对象，并将它们添加到聊天列表的顶部
 *
 * @param {string} value - 用户输入的消息内容
 * @param {object} context - 当前上下文，未在代码中使用，可能用于未来扩展
 */
const sendMessage = function (value, context) {
  // 检查是否正在加载流数据，如果是，则返回
  if (isStreamLoad.value) {
    return;
  }
  // 检查用户输入的消息是否为空，如果为空，则不执行任何操作
  if (!value) return;

  // 构建用户消息对象
  const userParams = {
    avatar: "https://tdesign.gtimg.com/site/avatar.jpg",
    name: "季舟",
    datetime: new Date().toDateString(),
    content: value,
    role: "user",
    variant: "outline",
  };

  // 将用户消息添加到聊天列表的顶部
  chatList.value.unshift(userParams);

  // 构建助手消息对象，内容为空，表示助手尚未回复
  const assitendParams = {
    avatar: "https://tdesign.gtimg.com/site/chat-avatar.png",
    name: "Ollama",
    datetime: new Date().toDateString(),
    content: "",
    role: "assistant",
    variant: "base",
  };

  // 将助手消息添加到聊天列表的顶部
  chatList.value.unshift(assitendParams);
  handleData(value);
};

/**
 * 处理数据函数，用于向服务器发送消息并处理返回的流式数据
 * @param {string} inputValue - 用户输入的消息内容
 */
const handleData = async (inputValue) => {
  // 设置加载状态为true
  loading.value = true;
  // 设置流式加载状态为true
  isStreamLoad.value = true;
  // 获取聊天列表的最后一个项
  const lastItem = chatList.value[0];
  // 构造消息对象，包含用户角色和内容
  const messages = [
    {
      role: "user",
      content: inputValue,
    },
  ];

  // 调用服务器发送事件流（SSE）接口获取数据
  fetchSSE(messages, {
    // 接收到消息时的处理函数
    onMessage(message) {
      console.log("message", message);
      // 设置加载状态为false
      loading.value = false;
      // 将接收到的消息内容追加到聊天列表的最后一个项中
      lastItem.content += message;
    },
    // 发生错误时的处理函数
    onError(error) {
      // 设置消息角色为错误
      lastItem.role = "error";
      // 设置消息内容为错误信息
      lastItem.content = error;
      // 设置流式加载状态为false
      isStreamLoad.value = false;
      // 设置加载状态为false
      loading.value = false;
    },
    // 数据接收完成时的处理函数
    onComplete() {
      // 设置流式加载状态为false
      isStreamLoad.value = false;
      // 设置加载状态为false
      loading.value = false;
    },
  });
};

/**
 * 通过Server-Sent Events (SSE) 流式获取数据
 *
 * @param {Array} messages 消息列表，包含聊天历史或查询信息
 * @param {Object} options 配置对象，包含事件处理函数
 * @param {Function} options.onMessage 接收到消息时的回调函数
 * @param {Function} options.onError 发生错误时的回调函数
 * @param {Function} options.onComplete 流式传输完成时的回调函数
 */
const fetchSSE = async (messages, options) => {
  // 解构配置对象中的事件处理函数
  const { onMessage, onError, onComplete } = options;

  // 创建AbortController以支持取消请求
  const controller = new AbortController();
  // 获取AbortSignal用于fetch请求
  const { signal } = controller;
  // 将控制器赋值给全局或父作用域变量，以便于外部取消请求
  fetchAbort.value = controller;

  const model = selectValue.value.value;

  // 发起fetch请求，获取SSE响应
  const resPromise = fetch("http://localhost:11434/api/chat", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      messages, // 消息列表
      model: model, // 模型
      stream: true, // 流式
    }),
    signal,
  });

  // 处理响应
  resPromise
    .then(async (response) => {
      // console.log("response", response);

      // 检查HTTP状态码，如果非200，则调用错误处理函数并抛出错误
      if (!response.ok) {
        const { status } = response;
        onError(`HTTP error! status: ${status}`);
        throw new Error(`HTTP error! status: ${status}`);
      }

      // 获取响应体的Reader对象，用于逐块读取数据
      const reader = response.body.getReader();
      // 创建TextDecoder对象，用于解码数据块
      const decoder = new TextDecoder("utf-8");
      // 初始化缓冲区
      let buffer = "";
      // 循环读取数据块
      while (true) {
        // 读取下一数据块
        const { done, value } = await reader.read();
        // 如果done为true，则表示数据读取完毕
        if (done) {
          console.log("Stream complete");
          // 调用完成处理函数
          onComplete();
          return Promise.resolve();
        }

        // 将数据块解码为字符串并追加到缓冲区
        buffer += decoder.decode(value, { stream: true });
        // 按行分割缓冲区内容
        const lines = buffer.split("\n");
        // 更新缓冲区内容，保留未完整的一行
        buffer = lines.pop();
        // 遍历每一行数据
        for (const line of lines) {
          // 忽略空行
          if (line.trim() === "") continue;
          try {
            // 解析JSON数据
            const chunk = JSON.parse(line);
            // 如果数据块表示传输完成，则跳出循环
            if (chunk.done) {
              break;
            }
            // 如果数据块包含消息内容，则调用消息处理函数
            if (chunk.message && chunk.message?.content) {
              const { content } = chunk.message;
              // 调用消息处理函数
              onMessage(content);
            }
          } catch (error) {}
        }
      }
    })
    .catch((error) => {
      // 错误处理
      console.log("error", error);
    });
};

/**
 * 停止加载操作
 *
 * 此函数用于中断正在进行的加载过程它通过调用fetchAbort.value.abort()来终止网络请求，
 * 并将isStreamLoad.value和loading.value设置为false，以更新加载状态
 */
const stopLoad = function () {
  console.log("onStop");
  fetchAbort.value.abort();
  isStreamLoad.value = false;
  loading.value = false;
};
</script>
